<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>钉钉滚动动画</title>
</head>
<body>
<!-- 页面头部 -->
<div class="header">HEADER</div>

<!-- 滚动动画的主要容器 -->
<div class="playground">
    <!-- 固定在视口顶部的动画容器 -->
    <div class="animation-container">
        <!-- 包含动画项的网格布局 -->
        <div class="list">
            <!-- 动画项，每个项有一个data-order属性用于控制动画顺序 -->
            <div data-order="0" class="list-item"></div>
            <div data-order="1" class="list-item"></div>
            <div data-order="2" class="list-item"></div>
            <div data-order="3" class="list-item"></div>
            <div data-order="2" class="list-item"></div>
            <div data-order="1" class="list-item"></div>
            <div data-order="0" class="list-item"></div>
            <div data-order="0" class="list-item"></div>
            <div data-order="1" class="list-item"></div>
            <div data-order="2" class="list-item"></div>
            <div data-order="3" class="list-item"></div>
            <div data-order="2" class="list-item"></div>
            <div data-order="1" class="list-item"></div>
            <div data-order="0" class="list-item"></div>
        </div>
    </div>
</div>

<!-- 页面底部 -->
<div class="footer">FOOTER</div>

<style>    /* 重置所有元素的内外边距和盒模型 */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
/* 隐藏水平滚动条 */
html {
    overflow-x: hidden;
}
/* 头部和底部样式，高度为视口高度，居中显示文本 */
.header,
.footer {
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 4em;
}
/* 滚动区域，高度为4000px，背景色为黑色 */
.playground {
    height: 4000px;
    background: #000;
}

/* 动画容器，固定在视口顶部 */
.animation-container {
    position: sticky;
    height: 100vh;
    top: 0;
}

/* 动画项列表，绝对定位在容器中央，使用网格布局 */
.list {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 80%;
    aspect-ratio: 2/1;
    border-radius: 10px;
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    grid-template-rows: repeat(2, 1fr);
    place-items: center;
}

/* 动画项样式，白色背景，圆形 */
.list-item {
    width: 60%;
    aspect-ratio: 1/1;
    background: #fff;
    border-radius: 10px;
}

/* 动画项背景渐变色 */
.list-item:nth-child(3n + 1) {
    background: linear-gradient(#3e90f7, #246bf6);
}
.list-item:nth-child(3n + 2) {
    background: linear-gradient(#53b655, #469c50);
}
.list-item:nth-child(3n + 3) {
    background: linear-gradient(#f3a93c, #f4ad3d);
}
</style>

<script>    // 创建一个动画函数，根据输入的x值返回对应的y值
function createAnimation(xStart, xEnd, yStart, yEnd) {
    return function (x) {
        if (x <= xStart) {
            return yStart;
        }
        if (x >= xEnd) {
            return yEnd;
        }
        return yStart + ((x - xStart) / (xEnd - xStart)) * (yEnd - yStart);
    };
}

// 存储每个动画项的动画函数映射
const animationMap = new Map();
// 获取所有动画项元素
const items = document.querySelectorAll('.list-item');
// 获取滚动区域元素
const playGround = document.querySelector('.playground');
// 获取动画项列表元素
const list = document.querySelector('.list');

// 更新动画映射，根据滚动位置计算每个动画项的动画函数
function updateAnimationMap() {
    animationMap.clear();
    if (items.length === 0) {
        return;
    }
    // 获取滚动区域的边界矩形
    const playGroundRect = playGround.getBoundingClientRect();
    // 获取当前滚动位置
    const scrollY = window.scrollY;
    // 计算滚动区域的顶部和底部位置
    const playGroundTop = playGroundRect.top + scrollY;
    const playGroundBottom = playGroundRect.bottom + scrollY - window.innerHeight;

    // 获取动画项列表的边界矩形
    const listRect = list.getBoundingClientRect();
    for (let i = 0; i < items.length; i++) {
        const item = items[i];
        // 计算每个动画项的滚动开始和结束位置
        const scrollStart = playGroundTop + item.dataset.order * 600;
        const scrollEnd = playGroundBottom;
        // 获取动画项的尺寸和位置
        const itemWidth = item.clientWidth;
        const itemHeight = item.clientHeight;
        const itemLeft = item.offsetLeft;
        const itemTop = item.offsetTop;
        // 创建动画函数：透明度、缩放、X轴平移、Y轴平移
        const opacityAnimation = createAnimation(scrollStart, scrollEnd, 0, 1);
        const scaleAnimation = createAnimation(scrollStart, scrollEnd, 0.5, 1);
        const translateXAnimation = createAnimation(
            scrollStart,
            scrollEnd,
            listRect.width / 2 - itemLeft - itemWidth / 2,
            0
        );
        const translateYAnimation = createAnimation(
            scrollStart,
            scrollEnd,
            listRect.height / 2 - itemTop - itemHeight / 2,
            0
        );
        // 将动画函数存储在animations对象中
        const animations = {
            opacity: function (scorllY) {
                return opacityAnimation(scorllY);
            },
            transform: function (scorllY) {
                const scaled = scaleAnimation(scorllY);
                const x = translateXAnimation(scorllY);
                const y = translateYAnimation(scorllY);
                return `translate(${x}px, ${y}px) scale(${scaled})`;
            },
        };
        // 将动画项和对应的动画函数映射存储在animationMap中
        animationMap.set(item, animations);
    }
}
updateAnimationMap();

// 更新动画项的样式，根据滚动位置应用动画效果
function updateStyles() {
    const scrollY = window.scrollY;
    for (const [item, animations] of animationMap) {
        for (const prop in animations) {
            item.style[prop] = animations[prop](scrollY);
        }
    }
}

// 初始更新样式
updateStyles();

// 监听滚动事件，更新动画项的样式
window.addEventListener('scroll', updateStyles);

// 监听窗口大小变化事件，重新计算动画映射并更新样式
window.addEventListener('resize', () => {
    updateAnimationMap();
    updateStyles();
});
</script>
</body>
</html>